interface Maze {
  ctx: any,
  maze: Array<Array<string>>, //迷宫数据
  visited: Array<Array<boolean>>, //迷宫访问过的节点
  inMist: Array<Array<boolean>>, //迷雾数据
  queue: Array<any>, //辅助栈
  dirction: Array<Array<number>>, //方向数据
  m: number, //迷宫列
  n: number //迷宫行
  width: number, //迷宫宽
  height: number, //迷宫高
  road: string, //路
  wall: string //墙
  entryX: number, //入口x坐标
  entryY: number, //入口y坐标
  outX: number, //出口x坐标
  outY: number, //出口y坐标
  over: boolean //是否渲染完成
}

class Maze {
  constructor(m: number, n: number) {
    if (m % 2 === 0 || n % 2 === 0) {
      throw (new Error('迷宫行和列必须为奇数!'))
    }
    const cvs: any = document.getElementById('cvs')
    this.ctx = cvs.getContext('2d')
    this.width = cvs.width
    this.height = cvs.height
    this.m = m
    this.n = n
    this.wall = '#'
    this.road = '0'
    this.entryX = 0
    this.entryY = 1
    this.outX = m - 1
    this.outY = n - 2
    this.dirction = [[1, 0], [-1, 0], [0, 1], [0, -1]]
    this.over = false
    this.init()
  }

  sleep(time: number): Promise<null> {
    return new Promise((resolve: () => void) => setTimeout(() => resolve(), time))
  }

  init(): void {
    this.maze = new Array(this.m)
    this.visited = new Array(this.m)
    this.inMist = new Array(this.m)
    for (let m = 0; m < this.m; m++) {
      this.maze[m] = new Array(this.n)
      this.visited[m] = new Array(this.n)
      this.inMist[m] = new Array(this.n)
      for (let n = 0; n < this.n; n++) {
        if (m % 2 === 1 && n % 2 === 1) {
          this.maze[m][n] = this.road
        } else {
          this.maze[m][n] = this.wall
        }
        this.visited[m][n] = false
        this.inMist[m][n] = true
      }
    }
    this.maze[this.entryX][this.entryY] = this.road
    this.maze[this.outX][this.outY] = this.road
    this.calc(this.entryX + 1, this.entryY)
  }

  inArea(x: number, y: number): boolean {
    return x >= 0 && x < this.m && y >= 0 && y < this.n
  }

  async calc(x: number, y: number) {
    if (!this.inArea(x, y)) {
      return console.log(`(${x},${y})此点超出界限！`)
    }

    this.queue = new Array()
    this.queue.push([x, y])
    this.visited[x][y] = true
    this.openMist(x, y)
    this.render()
    while (this.queue.length !== 0) {
      let randomIndex = Math.floor(Math.random() * this.queue.length)
      let randomE = this.queue[randomIndex]
      let lastE = this.queue[this.queue.length - 1]
      this.queue[randomIndex] = lastE
      this.queue.pop()
      for (let i = 0; i < 4; i++) {
        let newX = randomE[0] + this.dirction[i][0] * 2
        let newY = randomE[1] + this.dirction[i][1] * 2
        this.openMist(newX, newY)
        if (this.inArea(newX, newY) && !this.visited[newX][newY]) {
          this.queue.push([newX, newY])
          this.visited[newX][newY] = true
          await this.sleep(10)
          this.update(randomE[0] + this.dirction[i][0], randomE[1] + this.dirction[i][1])
        }
      }
    }
    this.over = true
  }

  openMist(x: number, y: number): void {
    if (this.inArea(x, y)) {
      for (let m = x - 1; m <= x + 1; m++) {
        for (let n = y - 1; n <= y + 1; n++) {
          if (this.inArea(m, n)) {
            this.inMist[m][n] = false
          }
        }
      }
    }
  }

  update(x: number, y: number) {
    if (this.inArea(x, y)) {
      this.maze[x][y] = this.road;
    }
  }

  render(): void {
    this.ctx.clearRect(0, 0, this.width, this.height)
    this.ctx.beginPath();
    let w = this.width / this.m
    let h = this.height / this.n
    for (let m = 0; m < this.m; m++) {
      for (let n = 0; n < this.n; n++) {
        if (this.inMist[m][n]) {
          this.ctx.fillStyle = '#000'
        } else if (this.maze[m][n] === '#') {
          this.ctx.fillStyle = 'blue'
        } else if (this.maze[m][n] === '0') {
          this.ctx.fillStyle = '#fff'
        }
        let x = m * w
        let y = n * h
        this.ctx.fillRect(x, y, w, h);
      }
    }
    window.requestAnimationFrame(() => {
      if(!this.over) {
        this.render()
      }
    })
  }

}

export default Maze